Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add ArrayField #197

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open

add ArrayField #197

wants to merge 5 commits into from

Conversation

timgraham
Copy link
Collaborator

@timgraham timgraham commented Dec 10, 2024

Based on django.contrib.postgres.fields.ArrayField.

@timgraham timgraham force-pushed the arrayfield branch 2 times, most recently from e7088d1 to 6338305 Compare December 13, 2024 01:53
@timgraham timgraham force-pushed the arrayfield branch 4 times, most recently from 6a97c8e to 8a7d407 Compare January 1, 2025 15:03
Comment on lines +29 to +30
Specifies the underlying data type and behavior for the array. It
should be an instance of a subclass of
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming we cannot confirm embedded models until after the embedded model PR is merged.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I plan to merge this first, then return to EMF and decide what to do.

django_mongodb/base.py Outdated Show resolved Hide resolved
Copy link
Collaborator

@Jibola Jibola left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some clarifying questions and comments here. Since this is mirroring PostgreSQL.ArrayField I think i'll spend more time on the documentation and the testing the next go-around. So far so good!

Comment on lines 116 to 122
if isinstance(value, list | tuple):
# Workaround for https://code.djangoproject.com/ticket/35982
if isinstance(self.base_field, DecimalField):
return [self.base_field.get_db_prep_save(i, connection) for i in value]
return [self.base_field.get_db_prep_value(i, connection, prepared=False) for i in value]
return value
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the ticket is now marked as resolved and the change will now work with get_db_prep_value. Is this fix now a part of 5.1 or we still need to hold onto this workaround?

Copy link
Collaborator Author

@timgraham timgraham Jan 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fixed in Django 5.2. (This PR still targets Django 5.0.)

Comment on lines 248 to 258
"$eq": [
{
"$cond": {
"if": {"$eq": [lhs_mql, None]},
"then": False,
"else": {"$setIsSubset": [value, lhs_mql]},
}
},
True,
]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we not use $all here?
Here's the equivalent:

{ "$all": value }

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried it but where does lhs_mql go? return {lhs_mql: {"$all": value}} gives, for example, {'$match': {'$expr': {'$field': {'$all': [{'$literal': 2}]}}}} but MongoDB reports "Unrecognized expression '$field'". I think $all can only be used for find, not in aggregate.

super().__init__(**kwargs)
if min_length is not None:
self.min_length = min_length
self.validators.append(ArrayMinLengthValidator(int(min_length)))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can come as anything other than int | None I or is this just an easy way to raise a type validation with int casting?

Copy link
Collaborator Author

@timgraham timgraham Jan 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess so. This doesn't look like typical Django code. As I recall, this code was merged a bit hastily as part of a massive PR during a sprint in 2014 without my careful review. I haven't reviewed the forms side of this PR carefully yet, and while doing so, I'll remove these casts (and probably propose they be removed upstream as well, considering there are no test failures without them).

edit: upon further examination, it was copied from CharField, and yes, it's for type validation: https://code.djangoproject.com/ticket/20440

Comment on lines 65 to 81
def validate(self, value):
super().validate(value)
errors = []
for index, item in enumerate(value):
try:
self.base_field.validate(item)
except ValidationError as error:
errors.append(
prefix_validation_error(
error,
prefix=self.error_messages["item_invalid"],
code="item_invalid",
params={"nth": index + 1},
)
)
if errors:
raise ValidationError(errors)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the validate functions here share a lot with the field class version. Any chance we could make a low-pri task for a mixin class?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are subtle differences. For example, the signature of validate() is different between form fields and model fields.

I wouldn't advise using mixins to share functionality between model fields and form fields because django.forms and django.db.models (and this project's corresponding modules) are loosely coupled.

django_mongodb/forms/array.py Outdated Show resolved Hide resolved
@timgraham timgraham force-pushed the arrayfield branch 2 times, most recently from 6cf9766 to 08ae4b4 Compare January 7, 2025 23:05
def as_mql(self, compiler, connection):
lhs_mql = process_lhs(self, compiler, connection)
value = process_rhs(self, compiler, connection)
return {"$and": [{"$ne": [lhs_mql, None]}, {"$setIsSubset": [lhs_mql, value]}]}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debugin the test model_fields_.test_arrayfield.QueryingTests.test_contains_subquery I find that the value must be checked for nullability also, sometimes subqueries will pass None as a value.
return {"$and": [{"$ne": [lhs_mql, None]}, {"$ne": [value, None]}, {"$setIsSubset": [value, lhs_mql]}]}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw, this PR could be merge as it is. that bug could be fixed latter. Just decide to fix this future issue now, or with the corresponding issue

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I figured contained_by had the same issue but the fix didn't work. Please take a look if you like.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will.

Copy link
Collaborator

@WaVEV WaVEV Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 I believe we found a MongoDB bug. 😬 the changes that you made should work, the other thing that I tried is to use the operator $isArray instead of checking for null, don't know if you want an exception if the one of the side isn't a array or a silent empty result.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants